package cn.yunyichina.provider.pay.service.impl;

import cn.yunyichina.provider.framework.dubbo.service.AlipayService;
import cn.yunyichina.provider.framework.dubbo.service.HymedService;
import cn.yunyichina.provider.framework.dubbo.service.PacityService;
import cn.yunyichina.provider.framework.dubbo.service.WechatPayService;
import cn.yunyichina.provider.framework.utils.UUIDUtil;
import cn.yunyichina.provider.iface.entity.base.OrderExtVo;
import cn.yunyichina.provider.iface.entity.common.ResCommon;
import cn.yunyichina.provider.iface.entity.common.ResponseObject;
import cn.yunyichina.provider.iface.entity.dto.hymed.PaymentRevokeRequestDTO;
import cn.yunyichina.provider.iface.entity.dto.hymed.PaymentRevokeResponseDTO;
import cn.yunyichina.provider.iface.entity.dto.pacity.PacityRefundRequestDTO;
import cn.yunyichina.provider.iface.entity.dto.pacity.PacityRefundResponseDTO;
import cn.yunyichina.provider.iface.entity.pay.PayCode;
import cn.yunyichina.provider.iface.entity.pay.RefundResponse;
import cn.yunyichina.provider.iface.entity.wechat.WechatMedicalRefundRequest;
import cn.yunyichina.provider.iface.entity.wechat.WechatMedicalRefundResponse;
import cn.yunyichina.provider.pay.constant.MethodRouter;
import cn.yunyichina.provider.pay.constant.ResponseEnum;
import cn.yunyichina.provider.pay.dao.PaymentJournalDao;
import cn.yunyichina.provider.pay.entity.PaymentJournal;
import cn.yunyichina.provider.pay.entity.PaymentSetting;
import cn.yunyichina.provider.pay.entity.notify.PayAttach;
import cn.yunyichina.provider.pay.exception.PaySettingNotFoundException;
import cn.yunyichina.provider.pay.service.BaseRefundService;
import cn.yunyichina.provider.pay.service.PaymentJournalService;
import cn.yunyichina.provider.pay.service.UnifiedRefundService;
import cn.yunyichina.provider.pay.utils.NumberUtil;
import cn.yunyichina.utils.commons.DateUtils;
import cn.yunyichina.utils.convert.JsonUtils;
import cn.yunyichina.utils.log.Log;
import cn.yunyichina.utils.log.constant.LogParamType;
import cn.yunyichina.utils.log.constant.LogScene;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 退费
 * Created by Lullaby on 2016/10/26.
 */
@Service
public class UnifiedRefundServiceImpl extends BaseRefundService implements UnifiedRefundService {

    private static final Logger logger = LoggerFactory.getLogger(UnifiedRefundServiceImpl.class);

    @Resource
    private PaymentJournalDao paymentJournalDao;

    @Resource
    private PaymentJournalService paymentJournalService;

    public ResponseObject refundUnified(String data) {
//        logger.info("UnifiedRefundServiceImpl.refundUnified.data -> {}", data);
        ResponseObject response = new ResponseObject();
        try {
            Map<String, String> params = JsonUtils.parseObject(data, Map.class);
            Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                    "UnifiedRefundServiceImpl.refundUnified.data -> " + data);
            String payCode = params.get("pay_code");
            switch (PayCode.valueOf(payCode)) {
                case WECHAT:
                    return wechatRefund(params);
                case WECHATMEDICAL:
                    return wechatMedicalRefund(params);
                case ALIPAY:
                    return alipayRefund(params);
                case PACITY:
                    return pacityRefund(params);
                case HYMEDUNION:
                    return hymedRefund(params);
                case UNION:
                    return unionRefund(params);
                default:
                    return response.buildResultCodeAndMessage(
                            ResponseEnum.REFUND_UNKNOWN_CHANNEL.getCode(),
                            ResponseEnum.REFUND_UNKNOWN_CHANNEL.getMessage()
                    );
            }
        } catch (PaySettingNotFoundException e) {
            logger.error("UnifiedRefundServiceImpl.refundUnified.PaySettingNotFoundException", e);
            return response.buildResultCodeAndMessage(
                    ResponseEnum.PAY_SETTING_NOT_FOUND.getCode(),
                    ResponseEnum.PAY_SETTING_NOT_FOUND.getMessage()
            );
        } catch (Exception e) {
            logger.error("UnifiedRefundServiceImpl.refundUnified.Exception", e);
            return response.buildResultCodeAndMessage(
                    ResponseEnum.REFUND_EXCEPTION.getCode(),
                    ResponseEnum.REFUND_EXCEPTION.getMessage()
            );
        }
    }

    private ResponseObject wechatRefund(Map<String, String> params) throws PaySettingNotFoundException {
        if (!checkAlipayOrWechatRefundParameters(params)) return invalidRefundParamsResponseObject();

        Map<String, String> map = buildWechatRefundParameters(params);
        String request = JsonUtils.toJsonString(map);
//        logger.info("UnifiedRefundServiceImpl.wechatRefund.request -> {}", request);
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatRefund.request -> " + request);
        ResponseObject response = WechatPayService.callUnderlineFromInternalObject(MethodRouter.WECHAT_REFUND, request);
//        logger.info("UnifiedRefundServiceImpl.wechatRefund.response -> {}", JsonUtils.toJsonString(response));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.RESPONSE, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatRefund.response -> " + JsonUtils.toJsonString(response));

        int refundAmount = Integer.valueOf(params.get("refund_fee"));

        if (response.isSuccess()) refundSuccessCallback(response, params, refundAmount);

        return response;
    }

    private ResponseObject wechatMedicalRefund(Map<String, String> params) throws PaySettingNotFoundException {
        if (!checkWechatMedicalRefundParameters(params)) return invalidRefundParamsResponseObject();

        String appid = params.get("app_id");

        PaymentSetting subSetting = this.getPaymentSetting(appid);
        if (subSetting == null) throw new PaySettingNotFoundException("SubSetting not found");
//        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.subSetting -> {}", JsonUtils.toJsonString(subSetting));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.NORMAL, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatMedicalRefund.subSetting -> " + JsonUtils.toJsonString(subSetting));
        PaymentSetting parentSetting = this.getPaymentSettingByParentId(subSetting.getParentId());
        if (parentSetting == null) throw new PaySettingNotFoundException("ParentSetting not found");
//        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.parentSetting -> {}", JsonUtils.toJsonString(parentSetting));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.NORMAL, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatMedicalRefund.parentSetting -> " + JsonUtils.toJsonString(parentSetting));

        Map<String, String> accessTokenParamsMap = new HashMap<>();
        accessTokenParamsMap.put("grant_type", "client_credential");
        accessTokenParamsMap.put("appid", subSetting.getPayAppId());
        accessTokenParamsMap.put("agentappid", parentSetting.getPayAppId());
        accessTokenParamsMap.put("agentsecret", parentSetting.getSecretKey());
        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.accessTokenParamsMap -> {}", JsonUtils.toJsonString(accessTokenParamsMap));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.NORMAL, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatMedicalRefund.parentSetting -> " + JsonUtils.toJsonString(parentSetting));

        String accessToken = wechatMedicalAccessToken(accessTokenParamsMap);
        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.accessToken -> {}", accessToken);
        if (StringUtils.isEmpty(accessToken))
            return new ResponseObject().buildResultCodeAndMessage(
                    ResponseEnum.WECHAT_ACCESS_TOKEN_FAILURE.getCode(),
                    ResponseEnum.WECHAT_ACCESS_TOKEN_FAILURE.getMessage()
            );

        params.put("appid", parentSetting.getPayAppId());
        params.put("sub_appid", subSetting.getPayAppId());
        params.put("mch_id", parentSetting.getMerchantAccount());
        params.put("sub_mch_id", subSetting.getMerchantAccount());

        WechatMedicalRefundRequest request = buildWechatMedicalRefundRequest(params, accessToken, parentSetting.getMedicarePayKey());
//        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.request -> {}", JsonUtils.toJsonString(request));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatMedicalRefund.request -> " + JsonUtils.toJsonString(request));
        WechatMedicalRefundResponse response = WechatPayService.getWechatPayClient().wechatMedicalRefund(request);
//        logger.info("UnifiedRefundServiceImpl.wechatMedicalRefund.response -> {}", JsonUtils.toJsonString(response));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.RESPONSE, params.get("order_no"),
                "UnifiedRefundServiceImpl.wechatMedicalRefund.response -> " + JsonUtils.toJsonString(response));

        if (response.isSuccess()) {
            PaymentJournal paymentJournal = new PaymentJournal();
            paymentJournal.setOrderNo(response.getPlatformOrderNo());
            paymentJournal.setPaymentDealId(response.getPayTransactionId());
            paymentJournal.setPayMode((short) PayCode.WECHATMEDICAL.getMode());
            paymentJournal.setPayStatus((short) 2);
            PaymentJournal existPaymentJournal = paymentJournalService.getPaymentJournal(paymentJournal);
            if (existPaymentJournal == null) {
                PayAttach payAttach = JsonUtils.parseObject(params, PayAttach.class);
                response.setPlatformSerialNumber(NumberUtil.buildPaymentDealNo());

                int refundAmount = Integer.valueOf(response.getRefundAmount());

                insertRefundPaymentJournal(response, payAttach, refundAmount);
            }

            return new ResponseObject().buildResultObject(response);
        }

        return new ResponseObject().buildResultCodeAndMessageAndResult(
                ResponseEnum.WECHAT_MEDICAL_REFUND_FAILED.getCode(),
                ResponseEnum.WECHAT_MEDICAL_REFUND_FAILED.getMessage(),
                response
        );
    }

    private String wechatMedicalAccessToken(Map<String, String> params) {
        logger.info("UnifiedRefundServiceImpl.wechatMedicalAccessToken.params -> {}", params);

        try {
            String accessTokenParamsJson = JsonUtils.toJsonString(params);
            logger.info("UnifiedRefundServiceImpl.wechatMedicalAccessToken.accessTokenParamsJson -> {}", accessTokenParamsJson);
            ResponseObject accessTokenResponse = WechatPayService.callUnderlineFromInternalObject(
                    MethodRouter.WECHAT_MEDICAL_ACCESS_TOKEN, accessTokenParamsJson
            );
            logger.info("UnifiedRefundServiceImpl.wechatMedicalAccessToken.accessTokenResponse -> {}", JsonUtils.toJsonString(accessTokenResponse));
            if (accessTokenResponse.isSuccess()) {
                Map<String, String> accessTokenResultMap = JsonUtils.parseObject(accessTokenResponse.getResultObject(), Map.class);

                return accessTokenResultMap.get("access_token");
            }
        } catch (Exception e) {
            logger.error("UnifiedRefundServiceImpl.wechatMedicalAccessToken.Exception", e);
        }

        return null;
    }

    private ResponseObject alipayRefund(Map<String, String> params) throws PaySettingNotFoundException {
        if (!checkAlipayOrWechatRefundParameters(params)) return invalidRefundParamsResponseObject();

        Map<String, String> map = buildAlipayRefundParameters(params);
        String request = JsonUtils.toJsonString(map);
//        logger.info("UnifiedRefundServiceImpl.alipayRefund.request -> {}", request);
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                "UnifiedRefundServiceImpl.alipayRefund.request -> " + JsonUtils.toJsonString(request));
        ResponseObject response = AlipayService.callUnderlineFromInternalObject(MethodRouter.ALIPAY_REFUND, request);
//        logger.info("UnifiedRefundServiceImpl.alipayRefund.response -> {}", JsonUtils.toJsonString(response));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                "UnifiedRefundServiceImpl.alipayRefund.response -> " + JsonUtils.toJsonString(response));

        int refundAmount = Integer.valueOf(params.get("refund_fee"));

        if (response.isSuccess()) refundSuccessCallback(response, params, refundAmount);

        return response;
    }

    private ResponseObject pacityRefund(Map<String, String> params) throws Exception {
        if (!checkPacityRefundParameters(params)) return invalidRefundParamsResponseObject();

        ResponseObject response = new ResponseObject();

        PacityRefundRequestDTO requestDTO = buildPacityRefundRequestDTO(params);
//        logger.info("UnifiedRefundServiceImpl.pacityRefund.requestDTO -> {}", JSON.toJSONString(requestDTO));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.REQUEST, params.get("order_no"),
                "UnifiedRefundServiceImpl.pacityRefund.requestDTO ->" + JSON.toJSONString(requestDTO));

        ResCommon<PacityRefundResponseDTO> refundResponse = PacityService.getPacityClient().doPacityRefund(requestDTO);
//        logger.info("UnifiedRefundServiceImpl.pacityRefund.refundResponse -> {}", JSON.toJSONString(refundResponse));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.RESPONSE, params.get("order_no"),
                "UnifiedRefundServiceImpl.pacityRefund.refundResponse -> -> " + JSON.toJSONString(refundResponse));


        if (refundResponse.isSuccess()) {
            PacityRefundResponseDTO responseDTO = refundResponse.getResult();

            if (responseDTO.isReturnCodeSuccess() && responseDTO.isResultCodeSuccess()) {
                RefundResponse refund = new RefundResponse();
                refund.setPlatformOrderNo(responseDTO.getPlatMchTradeNo());
                refund.setPlatformSerialNumber(responseDTO.getPlatMchRefundNo());
                refund.setPayTransactionId(responseDTO.getYztRefundNo());
                refund.setRefundAmount(responseDTO.getRefundAmt());
                if (!StringUtils.isEmpty(responseDTO.getRefundFinishTime())) {
                    refund.setRefundTime(DateUtils.strToDateTimeYMDHMSNotFilter(responseDTO.getRefundFinishTime()));
                } else {
                    refund.setRefundTime(DateUtils.getDateTimeStr());
                }

                response.setResultObject(refund);

                PayAttach attach = JsonUtils.parseObject(params, PayAttach.class);

                Integer refundAmount = new BigDecimal(requestDTO.getRefundAmt()).multiply(new BigDecimal(100)).intValue();

                insertRefundPaymentJournal(refund, attach, refundAmount);
            } else {
                response.setResultCodeAndMessageAndResult(
                        ResponseEnum.PACITY_REFUND_FAIL.getCode(),
                        ResponseEnum.PACITY_REFUND_FAIL.getMessage(),
                        responseDTO
                );
            }
        } else {
            response.setResultCodeAndMessage(refundResponse.getResultCode(), refundResponse.getResultMessage());
        }

        return response;
    }

    private ResponseObject hymedRefund(Map<String, String> params) throws PaySettingNotFoundException {
        String appCode = params.get("app_code");
        String orgCode = params.get("org_code");
        String payCode = params.get("pay_code");
        String orderNo = params.get("order_no");

        if (StringUtils.isEmpty(appCode) ||
                StringUtils.isEmpty(orgCode) ||
                StringUtils.isEmpty(orderNo)) {
            return invalidPayParamsResponseObject();
        }

        short payMode = (short) getPayModeByPayCode(payCode);

        PaymentJournal paymentJournal = new PaymentJournal();
        paymentJournal.setOrderNo(orderNo);
        paymentJournal.setPayStatus((short) 1);
        List<PaymentJournal> journals = paymentJournalDao.select(paymentJournal);
        if (CollectionUtils.isEmpty(journals)) {
            return new ResponseObject().buildResultCodeAndMessage(
                    ResponseEnum.NOT_FOUND_TRANSACTION.getCode(), ResponseEnum.NOT_FOUND_TRANSACTION.getMessage()
            );
        }

        PaymentSetting paymentSetting = getPaymentSetting(appCode, orgCode, payMode);
        PaymentJournal journal = journals.get(0);

        PaymentRevokeRequestDTO revokeRequestDTO = new PaymentRevokeRequestDTO();
        revokeRequestDTO.setAppCode(appCode);
        revokeRequestDTO.setOrgCode(orgCode);
        revokeRequestDTO.setPayCode(payCode);
        revokeRequestDTO.setMer_cd(paymentSetting.getMerchantAccount());
        revokeRequestDTO.setCt_orderid(journal.getPaymentDealId());

        ResCommon<PaymentRevokeResponseDTO> revokeResponse = HymedService.getHyMedClient().revokePayment(revokeRequestDTO);
        logger.info("UnifiedRefundServiceImpl.hymedRefund.revokeResponse -> {}", JsonUtils.toJsonString(revokeResponse));
        Log.i(LogScene.PAY_REFUND_UNIFIED, "refundUnified", LogParamType.RESPONSE, params.get("order_no"),
                "UnifiedRefundServiceImpl.hymedRefund.revokeResponse -> -> " + JsonUtils.toJsonString(revokeResponse));

        if (!revokeResponse.isSuccess()) {
            return new ResponseObject().buildResultCodeAndMessageAndResult(
                    revokeResponse.getResultCode(),
                    revokeResponse.getResultMessage(),
                    revokeResponse.getResult()
            );
        }

        PaymentRevokeResponseDTO revokeResponseDTO = revokeResponse.getResult();
        RefundResponse refundResponse = new RefundResponse();
        refundResponse.setPlatformOrderNo(orderNo);
        refundResponse.setPlatformSerialNumber(NumberUtil.buildPaymentDealNo());
        refundResponse.setPayTransactionId(revokeResponseDTO.getTraceno());
        refundResponse.setRefundAmount(String.valueOf(journal.getPayAmount()));
        refundResponse.setRefundTime(DateUtils.dateToString(Calendar.getInstance().getTime()));
        refundResponse.setDealAccount(journal.getDealAccount());

        PayAttach attach = JsonUtils.parseObject(params, PayAttach.class);

        insertRefundPaymentJournal(refundResponse, attach, null);

        return new ResponseObject().buildResultObject(refundResponse);
    }

    private ResponseObject unionRefund(Map<String, String> params) {
        logger.info("UnifiedRefundServiceImpl.unionRefund.params -> {}", JsonUtils.toJsonString(params));
        return new ResponseObject().buildResultCodeAndMessage(
                ResponseEnum.UNSUPPORTED.getCode(),
                ResponseEnum.UNSUPPORTED.getMessage()
        );
    }

    private void refundSuccessCallback(ResponseObject response, Map<String, String> params, int refundAmount) {
        insertRefundPaymentJournal(getRefundResponse(response), getPayAttach(params), refundAmount);
    }

    private RefundResponse getRefundResponse(ResponseObject response) {
        return JsonUtils.parseObject(response.getResultObject(), RefundResponse.class);
    }

    private PayAttach getPayAttach(Map<String, String> params) {
        return JsonUtils.parseObject(params, PayAttach.class);
    }

    private void insertRefundPaymentJournal(RefundResponse refundResponse, PayAttach payAttach, Integer refundAmount) {
        try {
            paymentJournalDao.insert(this.createRefundPaymentJournal(refundResponse, payAttach, refundAmount));
        } catch (Exception e) {
            logger.error("UnifiedRefundServiceImpl.insertRefundPaymentJournal.Exception", e);
        }
    }

    private PaymentJournal createRefundPaymentJournal(RefundResponse refundResponse, PayAttach payAttach, Integer refundAmount) {
        PaymentJournal journal = new PaymentJournal();

        journal.setId(UUIDUtil.get32UUID());
        journal.setPaymentDealNo(refundResponse.getPlatformSerialNumber());

        OrderExtVo orderExtVo = this.getBaseOrderByOrderNo(refundResponse.getPlatformOrderNo());

        journal.setOrgId(orderExtVo.getOrgId());
        journal.setOrgCode(orderExtVo.getOrgCode());
        journal.setOrgName(orderExtVo.getOrgName());
        journal.setPlatformId(orderExtVo.getPlatformId());
        journal.setPlatformName(orderExtVo.getPlatformName());
        journal.setPlatformOrgAppCode(orderExtVo.getPlatformOrgAppCode());
        journal.setPlatformOrgAppName(orderExtVo.getPlatformOrgAppName());
        journal.setBranchId(orderExtVo.getBranchId());
        journal.setBranchCode(orderExtVo.getBranchCode());
        journal.setBranchName(orderExtVo.getBranchName());
        journal.setPayAppId(orderExtVo.getPayAppId());
        journal.setOutOrderNo(orderExtVo.getOutOrderNo());
        journal.setOrderType(orderExtVo.getOrderType());

        int payMode = orderExtVo.getPayMode();

        if (PayCode.ALIPAYMEDICARE.getMode() == payMode || PayCode.WXMEDICALPAY.getMode() == payMode) {
            Integer payAmount = StringUtils.isEmpty(orderExtVo.getPayRealAmount()) ? orderExtVo.getPayAmount() : orderExtVo.getPayRealAmount();
            Integer insuranceAmount = StringUtils.isEmpty(orderExtVo.getInsuranceRealAmount()) ? orderExtVo.getInsuranceAmount() : orderExtVo.getInsuranceRealAmount();
            journal.setInsuranceMode(orderExtVo.getInsuranceMode());
            journal.setAccountAmount(insuranceAmount);
            journal.setMedicareAmount(orderExtVo.getMedicareAmount());
            journal.setInsuranceAmount(insuranceAmount);
            journal.setPayAmount(payAmount);
            journal.setTotalAmount(payAmount + insuranceAmount);
        } else {
            journal.setInsuranceMode(orderExtVo.getInsuranceMode());
            journal.setAccountAmount(0);
            journal.setMedicareAmount(0);
            journal.setInsuranceAmount(0);
            if (refundAmount == null) {
                journal.setPayAmount(orderExtVo.getPayAmount());
                journal.setTotalAmount(orderExtVo.getTotalAmount());
            } else {
                journal.setPayAmount(refundAmount);
                journal.setTotalAmount(refundAmount);
            }
        }

        journal.setDealAccount(refundResponse.getDealAccount());
        journal.setOrderNo(refundResponse.getPlatformOrderNo());
        journal.setPaymentDealId(refundResponse.getPayTransactionId());

        journal.setHisOrderNo(orderExtVo.getHisOrderNo());
        journal.setPayStatus((short) 2);
        journal.setCheckStatus((short) 0);
        journal.setSettlementStatus((short) 0);
        journal.setPayCode(payAttach.getPayCode());
        journal.setPayMode((short) getPayModeByPayCode(payAttach.getPayCode()));

        journal.setCreatedTime(DateUtils.getDatetime());
        journal.setPayTime(refundResponse.getRefundTime());

        return journal;
    }

}
